
#include "nane/InputSystem/Win32Mouse/Win32Mouse.h"
#include "nane/InputSystem/InputAxis.h"
#include "nane/WindowSystem/IWindowSystem.h"
#include "nane/core/assert.h"
#include "nane/core/log.h"

#include <algorithm>

namespace nane
{
    ////////////////////////////////////////////////////////////////////////////////////////////////
    static LRESULT CALLBACK s_wndProc( HWND _hWnd, UINT _msg, WPARAM _wParam, LPARAM _lParam )
    {
        Win32Mouse* win32Mouse = static_cast<Win32Mouse*>(::GetPropA(_hWnd, "NANE_WIN32MOUSE"));
        NANE_ASSERT_RV( win32Mouse != NULL, "Something very bad if this happened", 0 );
        return win32Mouse->WndProc(_hWnd, _msg, _wParam, _lParam);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    Win32Mouse::Win32Mouse()
        : m_hWnd( NULL )
        , m_wndProc( NULL )
        , m_oldMouseX( 0 )
        , m_oldMouseY( 0 )
        , m_currentMessage( 0 )
        , m_previousWasMouseMove( false )
        , m_secondEmptyPoll( false )
    {

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    Win32Mouse::~Win32Mouse()
    {

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    LRESULT Win32Mouse::WndProc( HWND _hWnd, UINT _msg, WPARAM _wParam, LPARAM _lParam )
    {
        NANE_ASSERT_RV( _hWnd == m_hWnd, "Window must be that we subscibed to", WinAPI::CallWindowProc(m_wndProc, _hWnd, _msg, _wParam, _lParam) );
        if( _msg == WM_MOUSEMOVE )
        {
            int32 newX = static_cast<int32>(static_cast<int16>(LOWORD(_lParam)));
            int32 newY = static_cast<int32>(static_cast<int16>(HIWORD(_lParam)));
            m_mouseMessages.push_back(MouseMessage(EMouseAxis::RelX, newX - m_oldMouseX));
            m_mouseMessages.push_back(MouseMessage(EMouseAxis::RelY, newY - m_oldMouseY));
            m_mouseMessages.push_back(MouseMessage(EMouseAxis::AbsX, newX));
            m_mouseMessages.push_back(MouseMessage(EMouseAxis::AbsY, newY));
            m_oldMouseX = newX;
            m_oldMouseY = newY;
            m_previousWasMouseMove = true;
        }
        else if( _msg == WM_MOUSEWHEEL )
        {
            int32 wheelDelta = static_cast<int32>(static_cast<int16>(HIWORD(_wParam)));
            m_mouseMessages.push_back(MouseMessage(EMouseAxis::Wheel, wheelDelta));
        }
        else if( _msg == WM_LBUTTONDOWN || _msg == WM_LBUTTONUP )
        {
            m_mouseMessages.push_back(MouseMessage(EMouseAxis::LeftButton, (_msg == WM_LBUTTONDOWN) ? 1 : 0));
        }
        else if( _msg == WM_RBUTTONDOWN || _msg == WM_RBUTTONUP )
        {
            m_mouseMessages.push_back(MouseMessage(EMouseAxis::RightButton, (_msg == WM_RBUTTONDOWN) ? 1 : 0));
        }
        else if( _msg == WM_MBUTTONDOWN || _msg == WM_MBUTTONUP )
        {
            m_mouseMessages.push_back(MouseMessage(EMouseAxis::MiddleButton, (_msg == WM_MBUTTONDOWN) ? 1 : 0));
        }

        return WinAPI::CallWindowProc(m_wndProc, _hWnd, _msg, _wParam, _lParam);
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool Win32Mouse::Initialize()
    {
        m_mouseMessages.reserve(4);
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void Win32Mouse::Shutdown()
    {
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    EInputDevice::raw_type Win32Mouse::GetDeviceType()
    {
        EInputDevice deviceType = EInputDevice::Mouse;
        return deviceType.raw_value();
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool Win32Mouse::InitializeContext( IWindow::OSHandle _windowHandle )
    {
        HWND hWnd = static_cast<HWND>(_windowHandle);
        if( ::SetPropA(hWnd, "NANE_WIN32MOUSE", static_cast<HANDLE>(this)) == FALSE )
        {
            LOG_ERROR("Failed to SetProp");
            return false;
        }
        m_hWnd = hWnd;
        m_wndProc = reinterpret_cast<WNDPROC>(WinAPI::SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(&s_wndProc)));
        return true;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    void Win32Mouse::ShutdownContext()
    {
        WinAPI::SetWindowLongPtr(m_hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(m_wndProc));
        m_wndProc = NULL;
        ::RemovePropA(m_hWnd, "NANE_WIN32MOUSE");
        m_hWnd = NULL;        
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
    bool Win32Mouse::Poll( byte* _deviceNum, byte* _axis, int32* _value )
    {
        if( m_currentMessage >= m_mouseMessages.size() )
        {
            m_mouseMessages.clear();
            m_currentMessage = 0;
            
            MSG msg;
            if( WinAPI::PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE) == TRUE )
            {
                ::TranslateMessage(&msg);
                WinAPI::DispatchMessage(&msg);
            }
        }
        if( m_mouseMessages.empty() && m_previousWasMouseMove )
        {
            if( m_secondEmptyPoll == true )
            {
                // generate 0 Relative Move
                m_mouseMessages.push_back(MouseMessage(EMouseAxis::RelX, 0));
                m_mouseMessages.push_back(MouseMessage(EMouseAxis::RelY, 0));
                m_previousWasMouseMove = false;
            }
            else
            {
                m_secondEmptyPoll = true;
            }
        }
        if( m_currentMessage < m_mouseMessages.size() )
        {
            *_deviceNum = 0;
            *_axis = m_mouseMessages[m_currentMessage].axis;
            *_value = m_mouseMessages[m_currentMessage].value;
            ++m_currentMessage;
            m_secondEmptyPoll = false;
            return true;
        }
        return false;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////
}   // namespace nane
